/* Display.cpp
Copyright (c) 2008 Michael Zahniser
Please note the license terms (MIT license) at the end of this document.

Function definitions for the Display class.
*/

#include "Display.h"

#include "Embedded.h"
#include "_sdl/EmbeddedFont.h"

#include <sstream>
#include <iostream>

using std::istringstream;
using std::cerr;
using std::endl;



// Constructor:
// Initialize the Display, loading resources from the given directory.
Display::Display(string resourcePath, string filePath)
	: isInitialized(false), scroll(0), mouseScroll(-1), scrollBarIsVisible(false)
{
	if(SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		cerr << "Unable to initialize SDL." << endl;
		return;
	}

	font.Load(EmbeddedFont::DATA, EmbeddedFont::WIDTH, EmbeddedFont::HEIGHT);
	icons.Load(Embedded::ICONS, Embedded::ICONS_WIDTH, Embedded::ICONS_HEIGHT);

	// Load the files.
	string home = getenv("HOME");
	helpEditor.InitializeFromData(Embedded::HELP);
	if(!preferencesEditor.Initialize(home + "/.hyperlist"))
		preferencesEditor.InitializeFromData(Embedded::PREFERENCES);
	// In the preferences, find the name of the most recent file.
	LoadFile(filePath);
	
	// If the user just started up for the first time, open the help file.
	bool isNoUserData = fileEditor.Categories().Name(0).empty();
	editor = isNoUserData ? &helpEditor : &fileEditor;

	screen.SetCaption("HyperList: " + fileEditor.FilePath());

	// Create the display based on the preferences.
	if(!LoadPreferences())
		return;

	// Draw the window.
	Draw();

	// Event handling settings.
	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
	SDL_SetEventFilter(&EventFilter);

	// The resources have been loaded successfully, and the window has been drawn.
	// From here on, if the user quits, changes ought to be saved.
	isInitialized = true;
}



// Destructor:
// Quit SDL, and save changes if the display initialized successfully.
// There's no harm in calling SDL_Quit() even if SDL_Init() failed, since it checks
// whether SDL is indeed running.
Display::~Display()
{
	SDL_Quit();

	if(isInitialized)
	{
		fileEditor.Save();
		preferencesEditor.Save();
	}
}



// Run:
// Begin the event loop.
// This function blocks until the user quits.
void Display::Run()
{
	// We cannot run if initialization failed.
	if(!isInitialized)
		return;

	while(true)
	{
		// Wait for the next event.
		SDL_Event event;
		SDL_WaitEvent(&event);

		// Skip redrawing if the handler for this event returns false (meaning
		// that the event resulted in no change in the display).
		if(event.type == SDL_QUIT)
			return;
		else if(event.type == SDL_KEYDOWN && !HandleKey(event))
			continue;
		else if(event.type == SDL_MOUSEBUTTONDOWN && !HandleButton(event))
			continue;
		else if(event.type == SDL_MOUSEMOTION && !HandleDrag(event))
			continue;

		// Redraw the screen only if a clause above did not continue past this.
		// That is, "continue" can be used above to skip redrawing if an event
		// turns out to have no effect.
		Draw();
	}
}



// Load the named file, and update the "Recent Files" preference.
void Display::LoadFile(string path)
{
	list<Task> &recent = preferencesEditor.GetCategory("Recent Files");
	// If no file is given, load the most recent one.
	if(path.empty())
	{
		list<Task>::const_iterator fileIt = recent.begin();
		++fileIt;
		if(fileIt == recent.end())
		{
			path = getenv("HOME");
			path += "/todo.hyperlist";
		}
		else
			path = fileIt->Text();
	}

	// Update the "recent files" list.
	list<Task> out;
	// Take the root task (the category heading).
	out.push_back(recent.front());
	out.begin()->SetExpandable();
	out.begin()->Expand();
	Task task;
	task.SetText(path);
	task.SetDepth(1);
	task.Reveal();
	out.push_back(task);
	
	// Remove duplicates of this path.
	list<Task>::iterator it = recent.begin();
	for(++it; it != recent.end(); ++it)
		if(it->Text() != path)
			out.push_back(*it);
	
	preferencesEditor.Update("Recent Files", out);
	
	fileEditor.Initialize(path);
}



// HandleKey:
// Handle a key press.
bool Display::HandleKey(const SDL_Event &event)
{
	// Any key event clears the mouse scrolling.
	mouseScroll = -1;
	
	// Get the key code and the modifier.
	SDLKey key = event.key.keysym.sym;
	SDLMod mod = event.key.keysym.mod;
	
	// If the control (or meta, Apple key on Macs) key is down, it is a system-level
	// command that may be handled here. Otherwise, the command will be handled by
	// the current Editor.
	bool controlKey = ((mod & (KMOD_META | KMOD_CTRL)) != 0);
	
	// Quit command.
	if(controlKey & (key == 'q'))
	{
		static SDL_Event quit;
		quit.type = SDL_QUIT;
		SDL_PushEvent(&quit);

		return false;
	}
	else if(controlKey & (key == 's'))
	{
		// Save command.
		// Don't save changes to the help file.
		if(editor == &helpEditor)
			return false;

		editor->Save();
		status = "Saved.";
	}
	else if(controlKey & (key == 'r'))
	{
		// Revert / resurrect.
		if(mod & KMOD_SHIFT)
		{
			editor->Resurrect();
			status = "Restored deleted categories.";
		}
		else
		{
			editor->Revert();
			status = "Reverted.";
		}
	}
	else if(controlKey & (key == 'h'))
		ToggleEditor(helpEditor);
	else if(controlKey & (key == 'p'))
		ToggleEditor(preferencesEditor);
	// If the event was none of the above, hand it along to the Editor.
	else
		return editor->HandleKey(key, mod);

	return true;
}



// HandleButton:
// Handle a button event.
bool Display::HandleButton(const SDL_Event &event)
{
	// Left mouse button.
	if(event.button.button == 1)
		return HandleClick(event);
	// Scroll wheel up or down.
	else if(event.button.button >= 4)
		return HandleScroll(event);

	// All other mouse events are not handled.
	return false;
}



// HandleClick:
// Handle a left mouse click.
bool Display::HandleClick(const SDL_Event &event)
{
	// Get the location of the click.
	int x = event.button.x;
	int y = event.button.y;
	
	// If the user clicks in the scroll bar, move it.
	if(scrollBarIsVisible & (x >= SCROLL_TREE_RECT.x + SCROLL_TREE_RECT.w))
		MoveScrollBar(y);
	else
	{
		// The user has clicked in the editor. Stop mouse scrolling.
		mouseScroll = -1;

		// If the user clicked in the category list, select a different category.
		if(x <= CATEGORY_RECT.w)
			editor->SelectCategory(y / ROW_HEIGHT);
		else
		{
			// If the user clicked in the task list, figure out which task was clicked
			// and what part of that task.
			y = (y / ROW_HEIGHT) + scroll;
			x -= CATEGORY_RECT.w;
			bool shiftKey = (SDL_GetModState() & KMOD_SHIFT) != 0;
			
			// Do nothing if the click was below the last task in the list.
			if(y >= editor->Tasks().Rows())
				return false;

			// Get the task that was clicked on.
			const Task &task = editor->Tasks()[y];
				
			// Figure out what part of the line was clicked on:
			// the expansion box, the check spot, the text, or the date.
			// Also handle shift + click specially, expanding the selection.
			int cx = ROW_HEIGHT * (task.Depth() - 1);
			if(x < cx) {}
			else if(x < (cx += ROW_HEIGHT))
				editor->ToggleExpansion(y, shiftKey);
			else if(x < (cx += ROW_HEIGHT))
				editor->ToggleCheck(y, shiftKey);
			else if(shiftKey)
				editor->ExpandSelection(y);
			else if(x < (cx += dimension["text padding"] + font.Width(task.Text())))
				editor->SelectText(y);
			else
				editor->SelectDate(y);
		}
	}

	return true;
}



// HandleScroll:
// Handle a scroll wheel event.
bool Display::HandleScroll(const SDL_Event &event)
{
	// Mouse wheel movement up and down is buttons 4 and 5.
	// Skip redrawing if the current view cannot be scrolled.
	if(!scrollBarIsVisible)
		return false;

	// Store the previous scoll position.
	// There is no need to redraw if this position does not change.
	int oldScroll = mouseScroll;

	// If we just started scrolling, begin at the current scroll position.
	if(mouseScroll == -1)
		mouseScroll = scroll;
	
	// Scroll up or down, limited by the visible row range.
	mouseScroll += dimension["mouse wheel speed"] * ((event.button.button == 5) - (event.button.button == 4));
	if(mouseScroll < 0)
		mouseScroll = 0;
	else if(mouseScroll > editor->Tasks().Rows() - VISIBLE_ROWS)
		mouseScroll = editor->Tasks().Rows() - VISIBLE_ROWS;

	// Redraw only if the scroll position has changed.
	return (mouseScroll != oldScroll);
}



// HandleDrag:
// Handle a mouse drag event.
bool Display::HandleDrag(const SDL_Event &event)
{
	// The filter ensures that this is a mouse drag event.
	// Skip redrawing if the scroll bar is not being dragged.
	if(mouseScroll == -1)
		return false;

	MoveScrollBar(event.motion.y);

	return true;
}



// LoadPreferences:
// Load any changes to the preferences file, updating the display accordingly.
bool Display::LoadPreferences()
{
	// Default settings:
	color.clear();

	dimension.clear();
	dimension["window width"] = 640;
	dimension["window height"] = 480;
	dimension["category width"] = 80;
	dimension["scroll bar width"] = 8;
	dimension["scroll bar padding"] = 2;
	dimension["text padding"] = 4;
	dimension["mouse wheel speed"] = 2;

	// Parse each category in the preferences, looking for categories named
	// "Colors" and "Dimensions". All others are ignored.
	for(int category = 0; category < preferencesEditor.Categories().Count(); ++category)
	{
		const TaskList &taskList = preferencesEditor.Categories()[category];
		
		if(taskList.Name() == "Colors")
			LoadPreferences(taskList, color);
		else if(taskList.Name() == "Dimensions")
			LoadPreferences(taskList, dimension);
	}
	
	// Resize the window based on the new settings.
	if(!SetDimensions())
		return false;

	// Update the icon and text colors.
	SetColors();

	return true;
}



// SetDimensions:
// Resze the window and the regions within it based on the preferences.
bool Display::SetDimensions()
{
	int width = dimension["window width"];
	int height = dimension["window height"];
	
	screen.Resize(width, height);
	// If we were unable to create a new window, bail out.
	if(!screen)
		return false;
	
	// Set the various dimensions and regions based on the preferences.
	ROW_HEIGHT = icons.Height();
	TEXT_Y_OFFSET = ROW_HEIGHT - font.Height();
	VISIBLE_ROWS = (height + ROW_HEIGHT - 1) / ROW_HEIGHT;

	CATEGORY_RECT.Set(
		0,
		0, 
		dimension["category width"] - 1,
		height);

	DIVIDER_RECT.Set(
		dimension["category width"] - 1,
		0, 
		1,
		height);

	TREE_RECT.Set(
		dimension["category width"],
		0, 
		width - dimension["category width"],
		height);

	SCROLL_TREE_RECT.Set(
		dimension["category width"],
		0, 
		TREE_RECT.w - dimension["scroll bar width"],
		height);

	SCROLL_BAR_RECT.Set(
		width - dimension["scroll bar width"],
		0, 
		dimension["scroll bar width"],
		height);

	STATUS_FRAME.Set(
		2 * dimension["scroll bar width"],
		height / 2 - ROW_HEIGHT, 
		width - 4 * dimension["scroll bar width"],
		2 * ROW_HEIGHT);

	STATUS_RECT.Set(
		STATUS_FRAME.x + 1,
		STATUS_FRAME.y + 1,
		STATUS_FRAME.w - 2,
		STATUS_FRAME.h - 2);
	
	// Tell all three editors what the number of visible rows is.
	// This is for handling the page up and page down keys.
	fileEditor.SetPageSize(VISIBLE_ROWS);
	helpEditor.SetPageSize(VISIBLE_ROWS);
	preferencesEditor.SetPageSize(VISIBLE_ROWS);

	return true;
}



// SetColors:
// Update the icon and text colors based on the user preferences.
void Display::SetColors()
{
	// Convert all colors into screen color values.
	for(map<string, Color>::const_iterator it = color.begin(); it != color.end(); ++it)
		it->second.Convert(screen);

	// Set the color of the font and icon glyphs.
	font.SetColor(color["text color"].Convert(screen));
	icons.SetColor(color["icon color"].Convert(screen));
}



// Draw:
// Redraw the screen.
void Display::Draw()
{
	DrawCategories();

	DrawScrollBar();

	// Draw the selection rectangles behind the text.
	DrawSelection();

	DrawTasks();

	DrawStatus();
	
	// Update the whole screen.
	screen.Update();
}



// DrawCategories:
// Draw the category list.
void Display::DrawCategories()
{
	screen.FillRect(CATEGORY_RECT, color["side back color"]);
	screen.VLine(DIVIDER_RECT.x, DIVIDER_RECT.y, DIVIDER_RECT.h, color["divider color"]);
	
	// Fill the selected category row.
	int selectedRow = editor->Categories().Selection();
	Rectangle rect(0, ROW_HEIGHT * selectedRow, CATEGORY_RECT.w, ROW_HEIGHT);
	screen.FillRect(rect, color["side select color"]);
	
	// This is the row whose name is being edited, if any.
	int editRow = -1;
	
	// Draw the text selection, if applicable.
	if(editor->IsEditingCategory())
	{
		editRow = selectedRow;
		
		// Get an iterator to the start of the string that is being edited.
		string::const_iterator it = editor->EditString().begin();
		
		// Get the location of the first selected character.
		rect.x = font.Width(it, it + editor->TextSelection().First()) + dimension["text padding"];
		
		// Get the position of the last selected character.
		rect.w = editor->TextSelection().IsSingle() ? 1 :
			font.Width(it + editor->TextSelection().First(), it + editor->TextSelection().Last());
		
		// Draw the selection highlight.
		screen.FillRect(rect, color["side edit color"]);
	}

	// Draw the actual text of all the category names.
	int catY = TEXT_Y_OFFSET;
	for(int row = 0; row < editor->Categories().Count(); ++row)
	{
		// If this is the row being edited, draw the edit string instead of the current name of the row.
		font.Draw((row == editRow) ? editor->EditString() : editor->Categories().Name(row), 
			screen, dimension["text padding"], catY);
		catY += ROW_HEIGHT;
	}
}



// DrawScrollBar:
// Draw the scroll bar, if it is visible, and update scrollBarIsVisible and the scroll position.
void Display::DrawScrollBar()
{
	if(UpdateScrollPosition())
	{
		// Draw the background of the bar.
		screen.FillRect(SCROLL_BAR_RECT, color["scroll back color"]);
		
		// Draw the slider.
		SDL_Rect bar = SCROLL_BAR_RECT;
		bar.h = (SCROLL_BAR_RECT.h * VISIBLE_ROWS) / editor->Tasks().Rows();
		bar.y = ((SCROLL_BAR_RECT.h - bar.h) * scroll) / (editor->Tasks().Rows() - VISIBLE_ROWS);
		bar.x += dimension["scroll bar padding"];
		bar.w -= 2 * dimension["scroll bar padding"];

		screen.FillRoundRect(bar, 1, color["scroll bar color"]);
	}
}



// UpdateScrollPosition:
// Update the scroll bar position. Unless the user is currently scrolling with the
// mouse, scroll to ensure that the currently selected line is on screen.
bool Display::UpdateScrollPosition()
{
	// Check whether the scroll bar is visible.
	bool visible = (editor->Tasks().Rows() > VISIBLE_ROWS);
	
	// Check to see if the scroll position should change.
	int newScroll = scroll;
	if(mouseScroll != -1)
		newScroll = mouseScroll;
	else if(!editor->Tasks().HasSelection())
		newScroll = 0;
	else
	{
		// Attempt to scroll so that the entire selection is visible on the screen.
		// If the selection is too big, scroll to the top of the selection.
		if(editor->Tasks().GetSelection().Last() >= newScroll + VISIBLE_ROWS)
			newScroll = editor->Tasks().GetSelection().Last() - (VISIBLE_ROWS - 1);

		if(editor->Tasks().GetSelection().First() < newScroll)
			newScroll = editor->Tasks().GetSelection().First();

		// Make sure the line of text we're editing is on the screen.
		if(editor->IsEditingRow())
		{
			if(editor->EditRow() >= newScroll + VISIBLE_ROWS)
				newScroll = editor->EditRow() - (VISIBLE_ROWS - 1);

			if(editor->EditRow() < newScroll)
				newScroll = editor->EditRow();
		}
	}

	// Return if the scroll has not changed (i.e. the scroll bar needs no updating).
	if((visible == scrollBarIsVisible) & (newScroll == scroll))
		return false;
	
	scrollBarIsVisible = visible;
	scroll = newScroll;

	return scrollBarIsVisible;
}



// DrawSelection:
// Fill in the highlightng for the selected region and for the text that is currently
// being edited. The text will later be drawn on top of this.
void Display::DrawSelection()
{
	// Draw the background of the tree display region.
	if(scrollBarIsVisible)
		screen.FillRect(SCROLL_TREE_RECT, color["main back color"]);
	else
		screen.FillRect(TREE_RECT, color["main back color"]);

	// If no row is selected, do nothing more.
	if(!editor->Tasks().HasSelection())
		return;
		
	const Task &task = editor->EditTask();

	// Fill in the selected row.
	Rectangle rect( 
		TREE_RECT.x, 
		ROW_HEIGHT * (editor->Tasks().GetSelection().First() - scroll), 
		TREE_RECT.w - dimension["scroll bar width"] * scrollBarIsVisible, 
		ROW_HEIGHT * editor->Tasks().GetSelection().Count());
	
	screen.FillRect(rect, color["main select color"]);
	
	// If no text is highlighted, return.
	if(!editor->IsEditingRow() & !editor->IsSearching())
		return;
		
	rect.y = ROW_HEIGHT * (editor->EditRow() - scroll);
	rect.h = ROW_HEIGHT;
	
	// Get an iterator to the text to highlight.
	string::const_iterator it = editor->IsSearching() ? task.Text().begin() : editor->EditString().begin();
	
	// Determine the beginning and end x position of the highlighted text.
	rect.x = TREE_RECT.x + ROW_HEIGHT * task.Depth() + ROW_HEIGHT + dimension["text padding"];
	
	bool isEditingDate = editor->IsEditingDate();
	if(isEditingDate)
	{
		rect.x += font.Width(task.Text());
		rect.x += font.Width(string(" ("));
	}
	
	rect.x += font.Width(it, it + editor->TextSelection().First(), isEditingDate);

	rect.w = editor->TextSelection().IsSingle() ? 1 :
		font.Width(it + editor->TextSelection().First(), it + editor->TextSelection().Last(), true);
	
	// Draw the highlight behind the text.
	screen.FillRect(rect, color["main edit color"]);
}



// DrawTasks:
// Draw the actual text and icons of the task list.
void Display::DrawTasks()
{
	// This rectangle will be the destination for all the drawing events.
	// Start at the top of the screen. Everything that is drawn will have the
	// height of a single row.
	SDL_Rect rect;
	rect.y = 0;
	rect.h = ROW_HEIGHT;
	
	// Get the index of the last visible row.
	int lastRow = editor->Tasks().Rows() - scroll;
	if(lastRow > VISIBLE_ROWS)
		lastRow = VISIBLE_ROWS;
	
	// Get the index of the row we are editing, if any.
	int editRow = -1;
	if(editor->IsEditingRow())
		editRow = editor->EditRow() - scroll;
	
	// This is the depth of the row at the top of the screen.
	// Lines filling in up to that depth must be drawn.
	int sideDepth = editor->Tasks()[scroll].Depth();
	for(int row = 0; row < lastRow; ++row)
	{
		// Get the task and number of children for this row.
		int rowIndex = row + scroll;
		const Task &task = editor->Tasks()[rowIndex];
		int children = editor->Tasks().Children(rowIndex);
		
		// Draw the tree segment for this task.
		rect.x = TREE_RECT.x + ROW_HEIGHT * (task.Depth() - 1);
		rect.y = ROW_HEIGHT * row;
		int icon = Icons::EXPANDABLE * task.IsExpandable() + Icons::EXPANDED * task.IsExpanded() + 
			Icons::LAST_LEAF * (!children | (editor->Tasks()[rowIndex + 1].Depth() < task.Depth()));
		icons.Draw(icon, screen, rect);
		
		// Store the start of this task line, in order to draw a strikethrough if necessary.
		rect.x += ROW_HEIGHT;
		int itemStartX = rect.x;

		// Draw the check box.
		icon = Icons::CIRCLE + Icons::CHECKED * task.IsChecked() + Icons::EMPHASIZED * task.IsEmphasized();
		icons.Draw(icon, screen, rect);
		
		// Draw the text. Special handling is necessary if this line is being edited.
		rect.x += ROW_HEIGHT + dimension["text padding"];
		if((row == editRow) & editor->IsEditingText())
			rect.x = font.Draw(editor->EditString(), screen, rect.x, rect.y + TEXT_Y_OFFSET);
		else
			rect.x = font.Draw(task.Text(), screen, rect.x, rect.y + TEXT_Y_OFFSET);
		string date = task.Date();
		bool editDate = ((row == editRow) & editor->IsEditingDate());
		if(editDate)
			date = editor->EditString();
		if(!date.empty() | editDate)
			rect.x = font.Draw(" (" + date + ")", screen, rect.x, rect.y + TEXT_Y_OFFSET);
		
		// Draw a strikethrough, if this line is deleted.
		if(task.IsDeleted())
			screen.HLine(itemStartX, rect.y + ROW_HEIGHT / 2, rect.x - itemStartX, color["text color"]);
		
		// Draw the continuation of the tree for this item (straight line, down)
		icon = Icons::VERTICAL_LINE;
		rect.x = itemStartX - ROW_HEIGHT;		
		for( ; children > 1; --children)
		{
			rect.y += ROW_HEIGHT;
			icons.Draw(icon, screen, rect);
		}
		
		// Draw a dotted line upwards if this task is more outdented than any previous one.
		if(task.Depth() < sideDepth)
		{
			sideDepth = task.Depth();
			rect.y = ROW_HEIGHT * row;
			while(rect.y > 0)
			{
				rect.y -= ROW_HEIGHT;
				icons.Draw(icon, screen, rect);
			}
		}
	}
}



// DrawStatus:
// If a status message has been set, display it. It is cleared on the next event.
void Display::DrawStatus()
{
	if(!status.empty())
	{
		// Draw the frame in which the message is displayed.
		screen.Frame(STATUS_FRAME, color["divider color"]);
		screen.FillRect(STATUS_RECT, color["status back color"]);

		// Center the message in the frame.
		int width = font.Width(status);
		int x = STATUS_RECT.x + (STATUS_RECT.w - width) / 2;
		int y = STATUS_RECT.y + (STATUS_RECT.h - font.Height()) / 2;
		
		// Draw the message.
		font.Draw(status, screen, x, y);

		// Now that it has been displayed, clear the message.
		status.clear();
	}
}



// LoadPreferences:
// Load preferences from a TaskList into a map.
// Elements with depth 1 are used as keys, and their children become their values.
template <class Type>
void Display::LoadPreferences(const TaskList &taskList, map<string, Type> &preferences)
{
	TaskList::const_iterator it = taskList.begin();
	TaskList::const_iterator end = taskList.end();
	
	string key;
	for(++it; it != end; ++it)
	{
		// If the depth is 1, this is a new key.
		if(it->Depth() == 1)
			key = it->Text();
		else
		{
			// If the depth is greater than 1, this is a new value.
			istringstream in(it->Text());
			// Whatever type is stored in the map must have a formatted extraction operator.
			in>>preferences[key];
		}
	}
}



// MoveScrollBar:
// Move the scroll bar based on the location of a mouse event,
// either a click in the scroll bar or a drag of it.
void Display::MoveScrollBar(int y)
{
	// Figure out where the bar should move to.
	int barHeight = (SCROLL_BAR_RECT.h * VISIBLE_ROWS) / editor->Tasks().Rows();
	int pixelRange = SCROLL_BAR_RECT.h - barHeight;
	int rowRange = editor->Tasks().Rows() - VISIBLE_ROWS;
	mouseScroll = (rowRange * (y - barHeight / 2)) / pixelRange;
	
	// Limit the scroll bar movement.
	if(mouseScroll < 0)
		mouseScroll = 0;
	else if(mouseScroll > rowRange)
		mouseScroll = rowRange;
}



// ToggleEditor:
// Toggle between the given editor and the main editor.
// If the previous editor was the preferences, apply the preference changes.
void Display::ToggleEditor(Editor &toggle)
{
	// If we are about to leave the preferences view, save the preferences.
	if(editor == &preferencesEditor)
		LoadPreferences();

	// If the given editor is selected, switch back to the main file editor.
	if(editor == &toggle)
		editor = &fileEditor;
	else
		editor = &toggle;

	// The editor does not remember the scroll state, because that is a
	// detail specific to the display. Scroll to the top of the list.
	scroll = 0;
}



// EventFilter:
// Filter that rejects all events not used by this application.
int Display::EventFilter(const SDL_Event *event)
{
	return (event->type == SDL_QUIT) | (event->type == SDL_MOUSEBUTTONDOWN) | (event->type == SDL_KEYDOWN) | 
		((event->type == SDL_MOUSEMOTION) & ((event->motion.state & SDL_BUTTON(1)) != 0));
}



/* Copyright (c) 2008 Michael Zahniser

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE. */
